package com.vhall.component.service.exam.impl;


import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Maps;
import com.vhall.component.dao.exam.AccountOfExamMapper;
import com.vhall.component.dao.exam.ExamAnswersMapper;
import com.vhall.component.dao.exam.ExamMapper;
import com.vhall.component.dao.exam.ExamRoomLkMapper;
import com.vhall.component.entity.account.AccountEntity;
import com.vhall.component.entity.common.CachePage;
import com.vhall.component.entity.exam.ExamAnswerEntity;
import com.vhall.component.entity.exam.ExamEntity;
import com.vhall.component.entity.exam.dto.*;
import com.vhall.component.entity.exam.vo.*;
import com.vhall.component.entity.export.ExportEntity;
import com.vhall.component.entity.page.vo.PageVO;
import com.vhall.component.entity.room.RoomExamLkEntity;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.support.Token;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.service.exam.ExamCacheService;
import com.vhall.component.service.exam.ExamConsoleService;
import com.vhall.component.service.exam.ExamRepository;
import com.vhall.component.service.exam.ExamService;
import com.vhall.component.service.export.ExportService;
import com.vhall.component.service.room.RoomInnerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.vhall.component.service.exam.constant.ExamConst.TYPE_EXAM;
import static com.vhall.component.service.exam.constant.ExamConst.TYPE_PAPER;

/**
 * @author yongzheng.xu
 * @since 2021/7/5
 */
@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamMapper, ExamEntity> implements ExamService, ExamRepository {

    @Autowired
    private RoomInnerService roomInnerService;

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private ExamRoomLkMapper examRoomLkMapper;

    @Autowired
    private PaasService paasService;

    @Autowired
    private ExamAnswersMapper examAnswersMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ExportService exportService;

    @Autowired
    private AccountOfExamMapper accountOfExamMapper;

    @Autowired
    private ExamRepository examRepository;

    @Autowired
    private ExamConsoleService examConsoleService;

    @Autowired
    private ExamCacheService examCacheService;

    @Override
    public ExamDTO paperCreate(Integer accountId, ExamPaperCreateReqVo vo) {

        LocalDateTime now = LocalDateTime.now();
        ExamEntity entity = new ExamEntity();
        ExamDTO dto = new ExamDTO();
        BeanUtils.copyProperties(vo, entity);
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        // 如果存在就编辑，不存在就插入
        if (null != exam) {
            // 考试类型确定后,不可修改
            entity.setExamId(exam.getExamId());
            entity.setType(exam.getType());
            entity.setSourceId(StringUtils.isEmpty(vo.getSourceId()) ? "" : vo.getSourceId());
            entity.setUpdatedAt(now);
            entity.setAccountId(accountId);
            examMapper.updateById(entity);
            BeanUtils.copyProperties(entity, dto);
            return dto;
        }
        // 类型为试卷时,不限时间
        if (Objects.equals(vo.getType(), 0)) {
            entity.setLimitTime(0);
        }
        entity.setSourceId(StringUtils.isEmpty(vo.getSourceId()) ? "" : vo.getSourceId());
        entity.setCreatedAt(now);
        entity.setUpdatedAt(now);
        entity.setAccountId(accountId);
        examMapper.insert(entity);
        BeanUtils.copyProperties(entity, dto);
        return dto;
    }

    @Override
    public ExamDTO paperInfo(Integer accountId, Integer examId) {
        ExamEntity exam = examMapper.selectById(examId);
        if (exam != null) {
            if (null != exam.getDeletedAt() || !exam.getAccountId().equals(accountId)) {
                return null;
            }
        }
        ExamDTO dto = new ExamDTO();
        BeanUtils.copyProperties(exam, dto);
        return dto;
    }

    @Override
    public PageVO<ExamDTO> paperList(Integer accountId, ExamPaperListReqVo vo) {
        QueryWrapper<ExamEntity> where = new QueryWrapper<>();
        where.and(StringUtils.isNotEmpty(vo.getKeyword()), wrapper ->
                wrapper.like("title", vo.getKeyword())
                        .or()
                        .like("exam_id", vo.getKeyword()));
        where.eq("account_id", accountId);
        where.eq("type", TYPE_PAPER);
        where.ge(StringUtils.isNotEmpty(vo.getBeginTime()), "created_at", vo.getBeginTime());
        if (StringUtils.isNotEmpty(vo.getEndTime())) {
            String endtime = getEndtime(vo.getEndTime());
            where.lt("created_at", endtime);
        }
        where.isNull("deleted_at");

        Integer total = examMapper.selectCount(where);

        List<ExamEntity> list = new ArrayList<>();
        if (total > 0) {
            where.orderByDesc("exam_id");
            where.last("limit " + vo.getPageSize() * (vo.getPage() - 1) + ", " + vo.getPageSize() + ";");
            list = examMapper.selectList(where);
        }

        List<ExamDTO> dtos = new ArrayList<>();
        for (ExamEntity entity : list) {
            ExamDTO dto = new ExamDTO();
            BeanUtils.copyProperties(entity, dto);
            dtos.add(dto);
        }

        PageVO<ExamDTO> result = new PageVO<>(vo.getPage(), vo.getPageSize(),
                total, dtos);
        return result;
    }

    @Override
    public ExamInfoDTO info(Integer accountId, ExamInfoVo vo) {
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        if (exam == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
        query.eq(RoomExamLkEntity::getExamId, vo.getExamId());
        RoomExamLkEntity rel = examRoomLkMapper.selectOne(query);
        if (rel == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        RoomsEntityDTO room = roomInnerService.getRoomInfo(rel.getRoomId());
        ExamInfoDTO end = new ExamInfoDTO();
        BeanUtils.copyProperties(exam, end);
        end.setRoomId(room.getRoomId());
        end.setSubject(room.getSubject());
        return end;
    }


    /**
     * 创建考试记录
     *
     * @param accountId
     * @param vo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ExamDTO create(Integer accountId, ExamCreateReqVo vo) {
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        RoomExamLkEntity erl = null;
        // 试卷存在情况
        if (exam != null) {
            if (exam.getType() != 1) {
                throw new BusinessException(BizErrorCode.TYPE_EXAM_ERROR);
            }
            LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
            query.eq(RoomExamLkEntity::getRoomId, vo.getRoomId());
            query.eq(RoomExamLkEntity::getExamId, vo.getExamId());
            erl = examRoomLkMapper.selectOne(query);
            if (erl.getPublish() == 1) {
                throw new BusinessException(BizErrorCode.COMP_EXAM_NOT_EDITABLE);
            }
        }

        // 创建试卷
        ExamPaperCreateReqVo examPaperCreateReqVo = new ExamPaperCreateReqVo();
        BeanUtils.copyProperties(vo, examPaperCreateReqVo);
        examPaperCreateReqVo.setType(1);
        ExamDTO dto = paperCreate(accountId, examPaperCreateReqVo);
        if (erl == null || !erl.getRoomId().equals(vo.getRoomId())) {
            bindRoom(vo.getExamId(), accountId, String.valueOf(vo.getRoomId()));
        }
        if (exam == null) {
            doCopy(vo.getExamId(), null, accountId, vo.getLimitTime());
        }
        return dto;
    }

    @Override
    public ExamDTO update(Integer accountId, ExamUpdateReqVo vo) {
        LambdaQueryWrapper<RoomExamLkEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RoomExamLkEntity::getExamId, vo.getOldExamId());
        wrapper.last("limit 1");
        RoomExamLkEntity older = examRoomLkMapper.selectOne(wrapper);
        if (older == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        if (older.getPublish() == 1) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_NOT_EDITABLE);
        }
        // 试卷存在情况
        ExamEntity ee = examMapper.selectById(vo.getExamId());
        if (ee != null && !Objects.equals(ee.getType(), 1)) {
            throw new BusinessException(BizErrorCode.TYPE_EXAM_ERROR);
        }
        LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
        query.eq(RoomExamLkEntity::getExamId, vo.getExamId());

        Integer count = examRoomLkMapper.selectCount(query);
        if (count > 0) {
            throw new BusinessException(BizErrorCode.TYPE_EXAM_EXIST_ERROR);
        }
        ExamPaperCreateReqVo pvo = new ExamPaperCreateReqVo();
        BeanUtils.copyProperties(vo, pvo);
        pvo.setType(1);
        ExamDTO oee = paperCreate(accountId, pvo);
        if (!older.getRoomId().equals(vo.getRoomId()) || !older.getExamId().equals(vo.getExamId())) {
            older.setRoomId(vo.getRoomId());
            older.setExamId(vo.getExamId());
            older.setUpdatedAt(LocalDateTime.now());
            examRoomLkMapper.updateById(older);
        }

        return oee;
    }

    /**
     * 由于创建试卷不用关联房间所以，作为列表没有用户和房间权限，所以、
     * 用了left join 要了exam全量
     *
     * @param vo
     * @return
     */
    @Override
    public PageVO<ExamListDTO> list(ExamListReqVo vo) {
        Page<ExamListDTO> expage = new Page<>();
        expage.setPages(vo.getPage());
        expage.setSize(vo.getPageSize());
        /**
         * 初始列表没有任何条件
         */
        IPage<ExamListDTO> exams = examMapper.selectInfoExam(expage, vo);
        HashMap<Integer, ExamAnswerEntity> ansmap = Maps.newHashMap();
        if (vo.getAnswerAccountId() != null) {
            List<Integer> list = exams.getRecords().stream().map(exam -> exam.getExamId()).collect(Collectors.toList());
            LambdaQueryWrapper<ExamAnswerEntity> query = Wrappers.lambdaQuery();
            query.eq(ExamAnswerEntity::getRoomId, vo.getRoomId());
            query.eq(ExamAnswerEntity::getAccountId, vo.getAnswerAccountId());
            query.in(!list.isEmpty(), ExamAnswerEntity::getExamId, list);
            List<ExamAnswerEntity> lists = examAnswersMapper.selectList(query);
            lists.forEach(e -> {
                ansmap.put(e.getExamId(), e);
            });
        }
        exams.getRecords().forEach(e -> e.setStatus(examStatus(e)));
        PageVO<ExamListDTO> result = new PageVO<>(vo.getPage(),
                vo.getPageSize(), Integer.parseInt(exams.getTotal() + ""), exams.getRecords());

        return result;
    }


    @Override
    public PageVO<ExamConsoleListRspVO> watchList(ExamWatchListReqVo vo) {
        vo.setPublish(1);
//        ExamListReqVo voe = new ExamListReqVo();
        ExamConsoleListReqVo voe = new ExamConsoleListReqVo();
        BeanUtils.copyProperties(vo, voe);
        // 此处和console的考试列表业务逻辑一样，所以直接调用
        return examConsoleService.list(voe);
    }

    @Override
    public Page<ExamAnswerEntity> gradeList(ExamGradeListReqVo vo) {
        //获取并验证信息
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        if (exam == null) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }
        RoomsEntityDTO roomInfoById = roomInnerService.getRoomInfo(vo.getRoomId());
        if (roomInfoById == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }

        List<ExamAnswerEntity> eas = examAnswersMapper.selectExamAnswers(vo);
        Page<ExamAnswerEntity> eapage = new Page<>();
        eapage.setTotal(eas.size());
        eapage.setRecords(eas);
        return eapage;
    }

    @Override
    public Boolean delete(Integer accountId, ExamDeleteReqVo vo) {
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        if (exam == null) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }
        if (!Objects.equals(exam.getAccountId(), accountId)) {
            throw new BusinessException(BizErrorCode.AUTH_LOGIN_NO_PERMISSION);
        }
        LambdaQueryWrapper<RoomExamLkEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RoomExamLkEntity::getExamId, vo.getExamId())
                .last("limit 1");
        RoomExamLkEntity lk = examRoomLkMapper.selectOne(wrapper);
        if (lk.getPublish().equals(1)) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_NOT_EDITABLE);
        }
        examMapper.deleteById(exam.getExamId());
        examRoomLkMapper.delete(wrapper);
        return true;

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean graded(ExamGradeReqVo vo) {

//        LambdaQueryWrapper<ExamAnswerEntity> wrapper = Wrappers.lambdaQuery();
//        wrapper.eq(ExamAnswerEntity::getOperatorAccountId, vo.getOperatorAccountId());
//        wrapper.last("limit 1")
//        ExamAnswerEntity one = examAnswersMapper.selectOne(wrapper);
//        if (one == null) {
//            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
//        }

        String key = RedisKey.EXAM_GRADED_MARK_LOCK + vo.getAnswerId();

        boolean lock = RedisLocker.tryLock(key, "1", 90000);
        if (!lock) {
            throw new BusinessException(BizErrorCode.BIZ_REPEAT_SUBMIT);
        }

        LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
        query.eq(RoomExamLkEntity::getExamId, vo.getExamId());
        query.eq(RoomExamLkEntity::getRoomId, vo.getRoomId());
        RoomExamLkEntity ree = examRoomLkMapper.selectOne(query);
        if (ree == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        if (ree.getIsGrade().equals(1)) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_FINISHED);
        }
        ExamAnswerEntity eae = examAnswersMapper.selectById(vo.getAnswerId());
        if (eae == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        JSONObject dealGradedMarkJson = dealGradedMark(vo.getGradedMark());
        Integer gradeScore = (Integer) dealGradedMarkJson.get("grade_score");
        Integer electScore = eae.getElectScore();
        Integer answerScore = gradeScore + electScore;
        eae.setAnswererScore(answerScore);
        eae.setGradedMark(vo.getGradedMark());
        eae.setIsGraded(1);
        eae.setOperatorAccountId(vo.getOperatorAccountId());
        eae.setOperatorNickName(vo.getOperatorNickname());
        examAnswersMapper.updateById(eae);
        return true;
    }

    private JSONObject dealGradedMark(String gradedMark) {
        JSONObject obj = new JSONObject();
        JSONArray jsonArray = JSONArray.parseArray(gradedMark);
        Integer score = 0;
        Integer gradeScore = 0;
        for (Object o : jsonArray) {
            JSONObject json = (JSONObject) o;
            score += (Integer) json.get("score");
            gradeScore += Integer.parseInt((String) json.get("gradeScore"));
        }
        obj.put("score", score);
        obj.put("grade_score", gradeScore);
        return obj;
    }

    @Override
    public GradeInfoRspVO gradedInfo(ExamGradeInfoReqVo vo) {
        HashMap<String, Object> data = Maps.newHashMap();
        ExamEntity exam = getExam(vo.getExamId(), null);
        ExamAnswerEntity answer = examAnswersMapper.selectById(vo.getAnswerId());
        if (answer == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        /**
         * 权限要求只有试卷创建者和答题者有权查询
         */
        if (!vo.getAccountId().equals(exam.getAccountId()) && !vo.getAccountId().equals(answer.getAccountId())) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_NO_ACCESS);
        }
        // {"answerContent":[{"ext":"","extension":"exam","replys":"A","isQuote":true,"type":"radio","title":"单选题","analysis":"","required":false,"imgUrl":"","score":10,"style":"","id":661957,"correctIndex":0,"items":[{"imgUrl":"","value":"选项","key":"A"},{"imgUrl":"","value":"选项","key":"B"}],"verification":false,"createDate":"2021-08-17 19:21:03","validate":{},"status":"submit"}],"answerResult":{"totalNum":"1","errNum":"0","electScore":"10","accuracy":"100","rightNum":"1","totalScore":"10","emptyNum":"0"}}

        // 组装返回结果
        ExamAnswersDTO answersDTO = new ExamAnswersDTO();
        BeanUtils.copyProperties(answer, answersDTO);
        ExamDTO examDTO = new ExamDTO();
        BeanUtils.copyProperties(exam, examDTO);
        if (StringUtils.isNotBlank(answer.getExtend())) {
            JSONObject answers = (JSONObject) JSONObject.parse(answer.getExtend());
            JSONObject answerResult = (JSONObject) answers.get("answerResult");
            answersDTO.setRightNum(answerResult.getInteger("rightNum"));
            answersDTO.setErrNum(answerResult.getInteger("errNum"));
            answersDTO.setEmptyNum(answerResult.getInteger("emptyNum"));
            answersDTO.setAccuracy(answerResult.getInteger("accuracy"));
            long answerTime = (long) answer.getEndTime() - answer.getStartTime();
            answersDTO.setAnswerTime(answerTime);
        }
        GradeInfoRspVO rspVO = new GradeInfoRspVO();
        rspVO.setExam(examDTO);
        rspVO.setAnswer(answersDTO);
        return rspVO;
    }

    @Override
    public HashMap<String, Object> stat(ExamVo vo) {
        if ("null".equals(vo.getRoomId())) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_IS_NOT_NULL);
        }
        HashMap<String, Object> data = Maps.newHashMap();
        LambdaQueryWrapper<RoomExamLkEntity> query = Wrappers.lambdaQuery();
        query.eq(RoomExamLkEntity::getRoomId, vo.getRoomId());
        query.eq(RoomExamLkEntity::getExamId, vo.getExamId());
        RoomExamLkEntity lk = examRoomLkMapper.selectOne(query);
        if (lk == null) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }
        RoomsEntityDTO room = roomInnerService.getRoomInfo(vo.getRoomId());
        ExamEntity exam = getExam(vo.getExamId(), null);
        String answererSetKey = RedisKey.EXAM_ANSWER_ACCOUNTS_SET + vo.getExamId();
        Long count = redisTemplate.opsForSet().size(answererSetKey);

        if (count == null || count == 0) {
            LambdaQueryWrapper<ExamAnswerEntity> queryrs = Wrappers.lambdaQuery();
            queryrs.eq(ExamAnswerEntity::getExamId, vo.getExamId());
            int effect = examAnswersMapper.selectCount(queryrs);
            count = Long.parseLong(effect + "");
        }
        //未批改 count
        LambdaQueryWrapper<ExamAnswerEntity> queryre = Wrappers.lambdaQuery();
        queryre.eq(ExamAnswerEntity::getExamId, vo.getExamId());
        queryre.eq(ExamAnswerEntity::getIsGraded, 0);
        Integer wcount = examAnswersMapper.selectCount(queryre);

        //已批改count
        LambdaQueryWrapper<ExamAnswerEntity> queryrw = Wrappers.lambdaQuery();
        queryrw.eq(ExamAnswerEntity::getExamId, vo.getExamId());
        queryrw.eq(ExamAnswerEntity::getIsGraded, 1);
        Integer ycount = examAnswersMapper.selectCount(queryrw);

        //平均分，最高分
        ExamStatDTO raw = examAnswersMapper.selectRaw(vo.getExamId());

        //考试状态
        ExamListReqVo reqVo = new ExamListReqVo();
        reqVo.setExamId(vo.getExamId());
        reqVo.setRoomId(vo.getRoomId());
        IPage<ExamListDTO> examListByPage = examMapper.selectInfoExam(new Page(), reqVo);

        examListByPage.getRecords().forEach(e -> {
            data.put("status", examStatus(e));
        });

        data.put("exam_id", vo.getExamId());
        data.put("title", exam.getTitle());
        data.put("room_id", vo.getRoomId());
        data.put("il_id", room.getIlId());
        data.put("subject", room.getSubject());
        data.put("score", exam.getScore());
        data.put("avg_score", raw == null ? 0 : raw.getAvgScore());
        data.put("max_score", raw == null ? 0 : raw.getMaxScore());
        data.put("min_score", raw == null ? 0 : raw.getMinScore());
        data.put("limit_time", exam.getLimitTime());
        data.put("submit_count", count);
        data.put("graded_count", ycount);
        data.put("ungraded_count", wcount);
        return data;
    }

    @Override
    public PageVO<ExamAnswersDTO> answeredList(ExamAnswerListVo vo) {
        ExamEntity exam = getExam(vo.getExamId(), vo.getRoomId());

        CachePage<ExamAnswersDTO> cachePage = examCacheService.getCachedAnsweredList(vo);
        if (Objects.isNull(cachePage)) {
            Page<ExamAnswersDTO> page = new Page<>();
            page.setSize(vo.getPageSize());
            page.setCurrent(vo.getPage());
            String cacheHashKey = examCacheService.getAnsweredListCacheHashKey(vo);
            if (!RedisLocker.tryLock(cacheHashKey, "1", 10 * 1000L)) {
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException e) {
                    log.error("Interrupted!", e);
                    Thread.currentThread().interrupt();
                }
                return answeredList(vo);
            }
            try {
                IPage<ExamAnswersDTO> exams = examAnswersMapper.joinRoomJoinsPage(page, vo);
                cachePage = examCacheService.cacheAnsweredList(vo, exams);

                log.info("缓存answered list完成，roomId={}, cacheHashKey={}", vo.getRoomId(), cacheHashKey);
            } finally {
                RedisLocker.unlock(cacheHashKey, "1");
            }
        }

        PageVO<ExamAnswersDTO> pageVO = new PageVO<>(cachePage.getCurrent(), vo.getPageSize(),
                Integer.parseInt(cachePage.getTotal() + ""), cachePage.getList());

        // {"answerContent":[{"ext":"","extension":"exam","replys":"A","isQuote":true,"type":"radio","title":"单选题","analysis":"","required":false,"imgUrl":"","score":10,"style":"","id":661957,"correctIndex":0,"items":[{"imgUrl":"","value":"选项","key":"A"},{"imgUrl":"","value":"选项","key":"B"}],"verification":false,"createDate":"2021-08-17 19:21:03","validate":{},"status":"submit"}],"answerResult":{"totalNum":"1","errNum":"0","electScore":"10","accuracy":"100","rightNum":"1","totalScore":"10","emptyNum":"0"}}
        Integer number = (vo.getPage() - 1) * vo.getPageSize() + 1;
        for (ExamAnswersDTO answersDTO : pageVO.getData()) {
            answersDTO.setNumber(number);
            answersDTO.setTotalScore(exam.getScore());
            answersDTO.setTotalNum(exam.getQuestionNum());
            if (StringUtils.isBlank(answersDTO.getExtend()) || "[]".equals(answersDTO.getExtend())) {
                continue;
            }
            JSONObject answers = (JSONObject) JSONObject.parse(answersDTO.getExtend());
            JSONObject answerResult = (JSONObject) answers.get("answerResult");
            answersDTO.setRightNum(answerResult.getInteger("rightNum"));
            answersDTO.setErrNum(answerResult.getInteger("errNum"));
            answersDTO.setEmptyNum(answerResult.getInteger("emptyNum"));
            answersDTO.setAccuracy(answerResult.getInteger("accuracy"));
            long answerTime = (long) answersDTO.getEndTime() - (long) answersDTO.getStartTime();
            answersDTO.setAnswerTime(answerTime);
            number++;
        }
        return pageVO;
    }

    @Override
    public Boolean exportAnswer(ExamAnswerExportReqVo vo) {
        ExamEntity ee = examMapper.selectById(vo.getExamId());
        if (ee == null) {
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        RoomsEntityDTO room = roomInnerService.getRoomInfo(vo.getRoomId());
        if (room == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        AccountEntity accountVO = accountOfExamMapper.selectById(vo.getAccountId());
        if (accountVO == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ACCOUNT);
        }
//        String fileName = accountVO.getUsername() + "_" + vo.getExamId() + "_exam_answer_list" + LocalDateTime.now().toString();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
        String fileName = accountVO.getUsername() + "_" + vo.getExamId() + "_exam_answer_list_" + LocalDateTimeUtil.format(LocalDateTime.now(), formatter);

        ExportEntity export = new ExportEntity();
        export.setExport("examAnswer");
        export.setIlId(room.getIlId());
        export.setAccountId(vo.getAccountId());
        export.setSourceId(String.valueOf(vo.getExamId()));
        export.setFileName(fileName);
        List<String> titles = new ArrayList<>();
        titles.add("直播ID");
        titles.add("直播名称");
        titles.add("考卷名称");
        titles.add("用户昵称");
        titles.add("用户账号");
        export.setTitle(JSON.toJSONString(titles));
        Map<String, Object> params = new HashMap<>(8);
        params.put("il_id", room.getIlId());
        params.put("account_id", vo.getAccountId());
        params.put("exam_id", vo.getExamId());
        params.put("room_id", room.getRoomId());
        if (vo.getAnswerId() != null) {
            params.put("answer_id", vo.getAnswerId());
        }
        export.setParams(JsonUtil.toJsonString(params));
        export.setCallback("examService:examAnswerExport");
        export.setRoomId(vo.getRoomId());
        return exportService.createExport(export);
    }

    @Override
    public boolean paperBind(Token token, ExamVo vo) {
        ExamEntity exam = examMapper.selectById(vo.getExamId());
        if (exam == null) {
            throw new BusinessException(BizErrorCode.EMPTY_EXAM);
        }
        // 如果房间号不为空, 房间中创建, 否则为试卷库
        RoomsEntityDTO room = roomInnerService.getRoomInfo(vo.getRoomId());
        if (null == room) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }

        //return bindRoom(vo.getExamId(), token.getAccountId(), vo.getRoomId());
        ExamDTO dto = doCopy(vo.getExamId(), vo.getRoomId(), token.getAccountId(), 0);
        return dto != null;
    }

    @Override
    public ExamDTO paperCopy(Integer accountId, ExamPaperCopyReqVo vo) {
        return doCopy(vo.getExamId(), vo.getRoomId(), accountId, 0);
    }


    private ExamDTO doCopy(Integer examId, String roomId, Integer accountId, Integer limitTime) {
        // 判断房间信息是否存在
        if (StringUtils.isNotBlank(roomId)) {
            RoomsEntityDTO room = roomInnerService.getRoomInfo(roomId);
            if (!Objects.equals(accountId, room.getAccountId())) {
                throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
            }
        }
        // 获取试卷信息
        ExamEntity exam = examMapper.selectById(examId);
        if (null == exam) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }
        // 从微吼云获取问卷详细信息
        JSONObject info = paasService.getFormInfo(String.valueOf(examId));
        Map<String, Object> paasObj = new HashMap<>(16);
        paasObj.put("title", info.getString("title"));
        paasObj.put("publish", "Y");
        paasObj.put("detail", formatDetail(info.getJSONArray("detail")));
        paasObj.put("owner_id", accountId);
        JSONObject newForm = paasService.createForm(paasObj);
        if (newForm == null || !newForm.containsKey("id")) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }

        // 复制试卷
        ExamPaperCreateReqVo examPaperCreateReqVo = new ExamPaperCreateReqVo();
        BeanUtils.copyProperties(exam, examPaperCreateReqVo);
        examPaperCreateReqVo.setExamId(newForm.getInteger("id"));
        examPaperCreateReqVo.setType(TYPE_PAPER);
        // 绑定房间的试卷类型为 考试
        if (StringUtils.isNotEmpty(roomId)) {
            examPaperCreateReqVo.setType(TYPE_EXAM);
            examPaperCreateReqVo.setLimitTime(limitTime == null ? 0 : limitTime);
        }
        ExamDTO dto = paperCreate(accountId, examPaperCreateReqVo);
        // 绑定房间
        if (StringUtils.isNotEmpty(roomId)) {
            bindRoom(newForm.getInteger("id"), accountId, roomId);
        }

        return dto;
    }

    private String formatDetail(JSONArray detail) {
        JSONArray array = new JSONArray();
        for (int i = 0; i < detail.size(); i++) {
            JSONObject obj = detail.getJSONObject(i);
            obj.remove("id");
            obj.remove("third_party_user_id");
            obj.remove("created_at");
            obj.remove("app_id");
            array.add(obj);
        }
        return JSON.toJSONString(array);
    }

    /**
     * 绑定问卷, 调用方法前, 确保 room id 存在
     *
     * @param examId    exam_id  在表 room_exam_lk 中唯一
     * @param accountId account id
     * @param roomId    room id
     * @return
     */
    public boolean bindRoom(Integer examId, Integer accountId, String roomId) {
        // 验证试卷信息
        getExam(examId, roomId);
        QueryWrapper<RoomExamLkEntity> where = new QueryWrapper<>();
        where.eq("exam_id", examId);
        //where.eq("room_id", roomId);
        where.last("limit 1");
        RoomExamLkEntity lk = examRoomLkMapper.selectOne(where);
        RoomExamLkEntity entity = new RoomExamLkEntity();

        int effect = 0;
        if (null == lk) {
            entity.setExamId(examId);
            entity.setAccountId(accountId);
            entity.setRoomId(roomId);
            entity.setBind(1);
            effect = examRoomLkMapper.insert(entity);
        } else {
            if (!Objects.equals(lk.getRoomId(), roomId)) {
                throw new BusinessException(BizErrorCode.COMP_EXAM_BIND_ROOM_FAILED);
            }
            entity.setId(lk.getId());
            entity.setBind(1);
            effect = examRoomLkMapper.updateById(entity);
        }
        return effect == 1;
    }

    private ExamEntity getExam(Integer examId, String roomId) {
        // ExamEntity exam = examMapper.selectById(examId);
        ExamEntity exam = examCacheService.getExam(examId);
        if (exam == null) {
            throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
        }
        if (roomId != null) {
            RoomsEntityDTO room = roomInnerService.getRoomInfo(roomId);
            if (room == null) {
                throw new BusinessException(BizErrorCode.EMPTY_ROOM);
            }
            if (!room.getAccountId().equals(exam.getAccountId())) {
                throw new BusinessException(BizErrorCode.COMP_EXAM_PAPER_NOT_EXIST);
            }
        }
        return exam;
    }

    @Override
    public Integer examStatus(ExamListDTO vo) {
        Integer status = 0;
        if (vo.getLkPublish() != null && vo.getLkPublish() == 0) {
            status = 1;
        } else if (vo.getLkPublish() != null && vo.getLkPublish() == 1 && vo.getLkIsFinish() != null && vo.getLkIsFinish() == 0) {
            status = 2;
        } else if (vo.getLkIsFinish() != null && vo.getLkIsFinish() == 1 && vo.getLkIsGrade() != null && vo.getLkIsGrade() == 0) {
            status = 3;
        } else if (vo.getLkIsGrade() != null && vo.getLkIsGrade() == 1 && vo.getLkIsPushGrade() != null && vo.getLkIsPushGrade() == 0) {
            status = 4;
        } else if (vo.getLkIsPushGrade() != null && vo.getLkIsPushGrade() == 1) {
            status = 5;
        }
        return status;
    }

    /**
     * 结束时间格式化
     *
     * @param endTime 结束时间
     * @return
     */
    private String getEndtime(String endTime) {
        //如果格式为yyyy-MM-dd，就直接加字符串;如果格式为yyyy-MM-dd hh:mm:ss,先截取字符串，再拼串
        if (endTime.length() > 10) {
            endTime = endTime.substring(0, 11);
        }
        return endTime + " 23:59:59";
    }
}
